// ==PREPROCESSOR==
// @name "WSH Playlist Viewer 2013"
// @version "0.0.8"
// @author "Br3tt aka Falstaff >> http://br3tt.deviantart.com"
// @feature "v1.4"
// @feature "watch-metadb"
// @feature "dragdrop"
// @import "%fb2k_profile_path%wsh_br3tt\js\WSHcommon.js"
// @import "%fb2k_profile_path%wsh_br3tt\js\WSHplaylist.js"
// ==/PREPROCESSOR==

// [Requirements]
// * foobar2000 v1.1 or better  >> http://foobar2000.org
// * WSH panel Mod v1.5.6 or better  >> http://code.google.com/p/foo-wsh-panel-mod/downloads/list
// * Font guifx v2 transports  >> http://blog.guifx.com/2009/04/02/guifx-v2-transport-font
//   this font is required to get nice stars for the rating columns, but if not installed, it will works with standard star (*) character
// [/Requirements]

// [Installation]
// * disable "Safe Mode" option in WSH Panel Mod preferences (menu File>Preferences>Tools>WSH Panel Mod)
// * copy "wsh_br3tt" folder into your foobar2000 Profile folder
// * then import/paste this script into a WSH Panel Mod instance of your foobar2000 layout (DUI or CUI)
// [/Installation]

// [Tips]
// * Use Jscript9 engine (if supported by your system) for better performances (known issue: high %cpu on mouse move with Windows 8 versions, prefer Jscript engine)
// * change colors and fonts in foobar2000 Preferences > DefaultUI or ColumsUI
// * Some Settings can be changed in window Properties (right click on a playlist item > Settings...)
// * double click on toolbar > Show Now Playing item
// * use Keyboard for searching artist in the playlist (incremental search feature like in ELPlaylist)
// [/Tips]

//=================================================// File Paths
var path_img = fb.ProfilePath+"\\wsh_br3tt\\images\\";

//=================================================// Properties
properties = {
    autocollapse: window.GetProperty("SYSTEM.Auto-Collapse", false),
    showgroupheaders : window.GetProperty("*GROUP: Show Group Headers", true),
    themed: window.GetProperty("CUSTOM Use Themed Style", true),
    showscrollbar: window.GetProperty("CUSTOM Show Scrollbar", true),
    showwallpaper: window.GetProperty("CUSTOM Show Wallpaper", false),
    showartistwallpaper: window.GetProperty("CUSTOM Use Artist Image as Wallpaper", false),
	wallpaperalpha: window.GetProperty("CUSTOM Wallpaper Alpha", 210),
	wallpaperpath: window.GetProperty("CUSTOM Default Wallpaper Path", ".\\wsh_br3tt\\images\\default.jpg"),
	highlightsearch: window.GetProperty("CUSTOM Highlight Search String", false),
	oddevenrowshighlight: window.GetProperty("CUSTOM Highlight Odd/Even Rows", true),
	showtooltip: false,
    settingspanel: false
}

//=================================================// Image declarations
images = {
    playicon_on: null,
    playicon_off: null,
    glass_reflect: null,
    nocover: null,
    noartist: null,
    stream: null
}

//=================================================// Titleformat field
var tf_group_key = fb.TitleFormat(window.GetProperty("*GROUP: group pattern", "%album artist%%album%%discnumber%"));
var tf_title = fb.TitleFormat("%title%");
var tf_album = fb.TitleFormat("%album%");
var tf_albumartist = fb.TitleFormat("$if(%length%,%album artist%,'Stream')");
var tf_track_meta = fb.TitleFormat("$if2($num(%tracknumber%,1),'-')^^%title%^^$if2(%play_counter%,$if2(%play_count%,0))^^$if(%length%,%artist%,'Stream')^^$if(%mood%,1,0)^^$if2(%rating%,0)^^$if2(%length%,' 0:00')^^$if(%__bitrate_dynamic%,$if(%el_isplaying%,%__bitrate_dynamic%'K',$if($stricmp($left(%codec_profile%,3),'VBR'),%codec_profile%,%__bitrate%'K')),$if($stricmp($left(%codec_profile%,3),'VBR'),%codec_profile%,%__bitrate%'K'))");
var tf_album_meta = fb.TitleFormat("$if(%length%,%album artist%,'Stream')^^$if(%album%,%album%$if(%discnumber%,$ifgreater(%totaldiscs%,1,' - [disc '%discnumber%$if(%totaldiscs%,'/'%totaldiscs%']',']'),),),$if(%length%,'-','Web radios'))^^$if2(%genre%,'Other')^^$if(%date%,$year($replace(%date%,/,-,.,-)),'-')");
var tf_playback_time_remaining = fb.TitleFormat("$if(%length%,-%playback_time_remaining%,'0:00')");
var tf_bitrate = fb.TitleFormat("$if(%__bitrate_dynamic%,$if(%el_isplaying%,%__bitrate_dynamic%'K',$if($stricmp($left(%codec_profile%,3),'VBR'),%codec_profile%,%__bitrate%'K')),$if($stricmp($left(%codec_profile%,3),'VBR'),%codec_profile%,%__bitrate%'K'))");
var tf_bitrate_playing = fb.TitleFormat("$if(%__bitrate_dynamic%,$if(%_isplaying%,$select($add($mod(%_time_elapsed_seconds%,2),1),%__bitrate_dynamic%,%__bitrate_dynamic%),%__bitrate_dynamic%),%__bitrate%)'K'");

//=================================================// Globals

// font vars
var g_font = null;
var g_font_headers = null;
var g_font_guifx_found = utils.CheckFont("guifx v2 transports");
var g_font_rating;
var g_font_mood;
var g_font_mood_outline;
// color vars
var g_color_normal_bg = 0;
var g_color_selected_bg = 0;
var g_color_normal_txt = 0;
var g_color_selected_txt = 0;
var g_color_highlight = 0;
var g_syscolor_button_bg = 0;
var g_syscolor_button_txt = 0;
// panel vars
var g_first_launch = true;
var g_instancetype = window.InstanceType;
var ww = 0, wh = 0;
var mouse_x = 0, mouse_y = 0;
var g_metadb;
var foo_playcount = utils.CheckComponent("foo_playcount", true);
var g_list = null;
var g_toolbar = null;
var g_topbar = null;
var g_seconds = 0;
var g_total_duration_text = "";
var g_mouse_wheel_timer = false;
var g_group_id_focused = 0;
clipboard = {
    selection: null
};
var g_1x1 = false;
// wallpaper vars
var g_wallpaper_img = null;
// Tooltip
var g_tooltip = window.CreateTooltip();
var g_tooltip_txt = "";
var g_show_tooltip = false;
// inputbox
var g_inputbox = null;
// wallpaper
var wpp_img_info = {orient: 0, cut: 0, cut_offset: 0, ratio: 0, x: 0, y: 0, w: 0, h: 0};

//=================================================// Item meta cache
item_cache = function () {
    this._cachelist = {};
    this.hit = function (metadb, tf) {
        var txt = this._cachelist[metadb.Path+tf];
        if (typeof(txt) == "undefined" || txt == null) { // if txt not in cache or reset to null on metadb changed
            txt = fb.TitleFormat(tf).EvalWithMetadb(metadb);
            this._cachelist[metadb.Path+tf] = txt;
            //fb.trace("NOT FOUND IN CACHE :"+txt);
        };
        return txt;
    };
    this.update = function (metadb, tf) {
        var txt = fb.TitleFormat(tf).EvalWithMetadb(metadb);
        this._cachelist[metadb.Path+tf] = txt;
    };
};
//var g_item_cache = new item_cache;

//=================================================// Images cache
function on_get_album_art_done(metadb, art_id, image, image_path) {
    for(var i = 0; i < g_list.items.length; i++) {
        if(g_list.items[i].metadb) {
            if(g_list.items[i].metadb.Compare(metadb)) {
                g_list.items[i].cover_img = g_image_cache.getit(metadb, g_list.items[i].tracktype, image);               
                var cx = g_list.items[i].x;
                var cy = g_list.items[i].y;
                // fix for a weird behaviour with engine Jscript9
                if(!cx) cx = 2; else if(cx<2) cx = 2;
                if(!cy) cy = 2; else if(cy<2) cy = 2;
                var cw = cover.column ? ((g_toolbar.columns[0].w <= cover.max_w) ? cover.max_w : g_toolbar.columns[0].w) : cover.max_w;
                if(!g_mouse_wheel_timer && !cScrollbar.timerID2 && !cList.repaint_timer) {
                    if(!cover.repaint_timer) {
                        cover.repaint_timer = window.SetTimeout(function() {
                            g_1x1 = false;
                            //cw = cover.column ? ((g_toolbar.columns[0].w <= cover.max_w) ? cover.max_w : g_toolbar.columns[0].w) : cover.max_w;
                            if(!g_mouse_wheel_timer && !cScrollbar.timerID2 && !cList.repaint_timer) window.RepaintRect(g_list.x, g_list.y, cw, g_list.h);
                            cover.repaint_timer && window.ClearTimeout(cover.repaint_timer);
                            cover.repaint_timer = false;
                        }, 75);
                    }
                    g_1x1 = true;
                    window.RepaintRect(0, 0, 1, 1);
                    g_1x1 = false;
                }
                break;
            };
        };
    };
};

//=================================================// Cover Tools
image_cache = function () {
    this._cachelist = {};
    this.hit = function (metadb) {
        var img = this._cachelist[metadb.Path];
        if (typeof img == "undefined") { // if image not in cache, we load it asynchronously
			if(!cover.load_timer) {
				cover.load_timer = window.SetTimeout(function() {
					switch(cGroup.type){
						case 0:
							var art_id = AlbumArtId.front;
							break;
						case 1:
							var art_id = AlbumArtId.artist;
							break;
						case 2:
							var art_id = AlbumArtId.front;
							break;
					};
					utils.GetAlbumArtAsync(window.ID, metadb, art_id, true, false, false);
					cover.load_timer && window.ClearTimeout(cover.load_timer);
					cover.load_timer = false;
				}, (g_mouse_wheel_timer || cScrollbar.timerID2 ? 20 : 10));
			};
        };
        return img;
    };
    this.getit = function (metadb, track_type, image) {
        var cw = cover.column ? ((g_toolbar.columns[0].w <= cover.max_w) ? cover.max_w : g_toolbar.columns[0].w) : cover.max_w;
        var ch = cw;
        var img;
        if(cover.keepaspectratio) {
            if(!image) {
                var pw = cw + cover.margin * 2;
                var ph = ch + cover.margin * 2;
            } else {
                if(image.Height>=image.Width) {
                    var ratio = image.Width / image.Height;
                    var pw = (cw + cover.margin * 2) * ratio;
                    var ph = ch + cover.margin * 2;
                } else {
                    var ratio = image.Height / image.Width;
                    var pw = cw + cover.margin * 2;
                    var ph = (ch + cover.margin * 2) * ratio;
                };
            };
        } else {
            var pw = cw + cover.margin * 2;
            var ph = ch + cover.margin * 2;
        };
        // cover.type : 0 = nocover, 1 = external cover, 2 = embedded cover, 3 = stream
        if(track_type!=3) {
            if(metadb) {
                img = FormatCover(image, pw, ph, cover.draw_glass_reflect, false);
                if(!img) {
                    img = (cGroup.type == 1) ? images.noartist : images.nocover;
                    cover.type = 0;
                } else {
                    cover.type = 1;
                };
            };
        } else {
            img = images.stream;
            cover.type = 3;
        };
        this._cachelist[metadb.Path] = img;
        return img;
    };
};
var g_image_cache = new image_cache;

function FormatCover(image, w, h, reflect, rawBitmap) {
	if(!image || w<=0 || h<=0) return image;
    if(reflect) {
        var new_img = image.Resize(w, h, 2);
        var gb = new_img.GetGraphics();
        if(h > w) {
            gb.DrawImage(images.glass_reflect, Math.floor((h-w)/2)*-1 + 1, 1, h - 2, h - 2, 0, 0, images.glass_reflect.Width, images.glass_reflect.Height, 0, 150);
        } else {
            gb.DrawImage(images.glass_reflect, 1, Math.floor((w-h)/2)*-1 + 1, w - 2, w - 2, 0, 0, images.glass_reflect.Width, images.glass_reflect.Height, 0, 150);
        };
        new_img.ReleaseGraphics(gb);
        if(rawBitmap) {
            return new_img.CreateRawBitmap();
        } else {
            return new_img;
        }
    } else {
        if(rawBitmap) {
            return image.Resize(w, h, 2).CreateRawBitmap();
        } else {
            return image.Resize(w, h, 2);
        }
    };

};


function reset_cover_timers() {
    cover.load_timer && window.ClearTimeout(cover.load_timer);
    cover.load_timer = false;
};

//=================================================// Colour & Font Callbacks
function on_font_changed() {
    get_font();
    window.Repaint();
};

function on_colors_changed() {
    get_colors();
    get_images();
    g_topbar.setButtons();
    if(g_list && g_list.totalRows > g_list.totalRowVisible) {
        g_scrollbar.setButtons();
        g_scrollbar.setCursorButton();
    }
	g_toolbar.setButtons();
    window.Repaint();
};

function get_font() {
	if (g_instancetype == 0) {
		g_font = window.GetFontCUI(FontTypeCUI.items, "{82196D79-69BC-4041-8E2A-E3B4406BB6FC}");
		g_font_headers = window.GetFontCUI(FontTypeCUI.labels, "{C0D3B76C-324D-46D3-BB3C-E81C7D3BCB85}");
	} else if (g_instancetype == 1) {
		g_font = window.GetFontDUI(FontTypeDUI.playlists);
		g_font_headers = window.GetFontDUI(FontTypeDUI.tabs);
	};
    if(g_font_guifx_found) {
        g_font_rating = gdi.Font("guifx v2 transports", 17, 0);
        g_font_mood = gdi.Font("guifx v2 transports", 16, 0);
        g_font_mood_outline = gdi.Font("guifx v2 transports", 17, 1);
    } else {
        g_font_rating = gdi.Font("tahoma", 22, 0);
        g_font_mood = gdi.Font("tahoma", 16, 1);
        g_font_mood_outline = g_font_mood;
    };
};

function get_colors() {
    // get some system colors
    g_syscolor_button_bg = utils.GetSysColor(COLOR_BTNFACE);
    g_syscolor_button_txt = utils.GetSysColor(COLOR_BTNTEXT);
    
    // get custom colors from window Properties first
    g_color_normal_txt = window.GetProperty("CUSTOM COLOR TEXT NORMAL", "");
    g_color_selected_txt = window.GetProperty("CUSTOM COLOR TEXT SELECTED", "");
    g_color_normal_bg = window.GetProperty("CUSTOM COLOR BACKGROUND NORMAL", "");
    g_color_selected_bg = window.GetProperty("CUSTOM COLOR BACKGROUND SELECTED", "");
    g_color_highlight = window.GetProperty("CUSTOM COLOR HIGHLIGHT", "");
    
    // get UI colors set in UI Preferences if no custom color set
	if (g_instancetype == 0) {
		if(g_color_normal_txt.length < 1) g_color_normal_txt = window.GetColorCUI(ColorTypeCUI.text); else g_color_normal_txt = eval(g_color_normal_txt);
		if(g_color_selected_txt.length < 1) g_color_selected_txt = window.GetColorCUI(ColorTypeCUI.selection_text); else g_color_selected_txt = eval(g_color_selected_txt);
		if(g_color_normal_bg.length < 1) g_color_normal_bg = window.GetColorCUI(ColorTypeCUI.background); else g_color_normal_bg = eval(g_color_normal_bg);
		if(g_color_selected_bg.length < 1) g_color_selected_bg = window.GetColorCUI(ColorTypeCUI.selection_background); else g_color_selected_bg = eval(g_color_selected_bg);
		if(g_color_highlight.length < 1) g_color_highlight = window.GetColorCUI(ColorTypeCUI.active_item_frame); else g_color_highlight = eval(g_color_highlight);
	} else if (g_instancetype == 1) {
		if(g_color_normal_txt.length < 1) g_color_normal_txt = window.GetColorDUI(ColorTypeDUI.text); else g_color_normal_txt = eval(g_color_normal_txt);
		if(g_color_selected_txt.length < 1) g_color_selected_txt = window.GetColorDUI(ColorTypeDUI.selection); else g_color_selected_txt = eval(g_color_selected_txt);
		if(g_color_normal_bg.length < 1) g_color_normal_bg = window.GetColorDUI(ColorTypeDUI.background); else g_color_normal_bg = eval(g_color_normal_bg);
		if(g_color_selected_bg.length < 1) g_color_selected_bg = g_color_selected_txt; else g_color_selected_bg = eval(g_color_selected_bg);
		if(g_color_highlight.length < 1) g_color_highlight = window.GetColorDUI(ColorTypeDUI.highlight); else g_color_highlight = eval(g_color_highlight);
	};

};

//=================================================// Init
function on_init() {
    
    // check properties
    if(!properties.showgroupheaders) {
        cGroup.collapsed_height = 0;
        cGroup.expanded_height = 0;
    }
    
    if(plman.PlaylistCount > 0) {
        g_list = new oList(plman.ActivePlaylist, tf_group_key);
    }
    get_font();
    get_colors();
    get_images();
    
    g_topbar = new oTopbar();
    g_toolbar = new oToolbar();
    g_scrollbar = new oScrollbar(properties.themed);
    
	//g_inputbox = new oInputbox(200, 20, "", "enter text", RGB(50,50,50), RGB(255,255,255), RGB(0,0,0), RGB(175,200,240), action_inputbox);
};
on_init();

function action_inputbox() {
	fb.trace("test!");
}

//=================================================// OnSize
function on_size() {
    if (!window.Width || !window.Height) return;
    
    window.DlgCode = DLGC_WANTALLKEYS;
       
    if(g_instancetype == 0) { // CUI
        window.MinWidth = 300;
        window.MinHeight = 200;  
    } else if(g_instancetype == 1) { // DUI
        window.MinWidth = 300;
        window.MinHeight = 200;
    };
    
    ww = window.Width;
    wh = window.Height;
    
    // topbar default height ?
    if(cTopbar.locked) {
        var topbar_h = cTopbar.height;
        g_topbar.visible = true;
    } else {
        var topbar_h = 0;
        g_topbar.visible = false;
    }
    
    // toolbar default height / autohide properties status ?
    if(cToolbar.locked) {
        var toolbar_h = cToolbar.height;
        g_toolbar.visible = true;
    } else {
        var toolbar_h = 1;
        g_toolbar.visible = false;
    }
    
    // set Size of Topbar
    g_topbar.setSize(0, 0, ww, topbar_h);
    
    // set Size of Toolbar
    g_toolbar.setSize(0, topbar_h, ww - cScrollbar.width, toolbar_h);
    g_toolbar.setColumns();
    
    // set Size of list
    g_list.setSize(0, topbar_h + toolbar_h, ww, wh - toolbar_h - topbar_h);
    g_list.setItems(true);
    
    // set Size of scrollbar
    g_scrollbar.setSize(ww - cScrollbar.width, topbar_h + toolbar_h, cScrollbar.width, wh - toolbar_h - topbar_h);
    g_scrollbar.setCursor(g_list.totalRowVisible, g_list.totalRows, g_list.offset);
    
    // set wallpaper
    if(fb.IsPlaying) {
        g_wallpaper_img = setWallpaperImg(properties.wallpaperpath, fb.GetNowPlaying());
    } else {
        g_wallpaper_img = setWallpaperImg(properties.wallpaperpath, null);
    }
    
    // Set the empty rows count in playlist setup for cover column size!
    if(g_toolbar.columns[0].w > 0) {
        cover.column = true;
        cGroup.count_minimum = Math.ceil( (g_toolbar.columns[0].w) / cTrack.height);
        if(cGroup.count_minimum < cGroup.default_count_minimum) cGroup.count_minimum = cGroup.default_count_minimum;
    } else {
        cover.column = false;
        cGroup.count_minimum = cGroup.default_count_minimum;
    }
    cover.previous_max_size = g_toolbar.columns[0].w;
    update_playlist();
    
};

//=================================================// OnPaint
function on_paint(gr) {
    
    if(!g_1x1) {

        // draw background
        if(g_wallpaper_img && properties.showwallpaper) {
            gr.GdiDrawBitmap(g_wallpaper_img, g_list.x, g_list.y, g_list.w, g_list.h, 0, 0, g_wallpaper_img.Width, g_wallpaper_img.Height);
            gr.FillSolidRect(g_list.x, g_list.y, g_list.w, g_list.h, g_color_normal_bg & RGBA(255,255,255,properties.wallpaperalpha));
        } else {
            gr.FillSolidRect(g_list.x, g_list.y, g_list.w, g_list.h, g_color_normal_bg);
        }
                
        // Set columns pos and width before drawing item!
        g_toolbar.set(gr);
        
        // playlist
        if(g_list) {
            if(g_list.count > 0) {
                g_list.draw(gr);
            } else {
                if(fb.PlaylistCount > 0) {
                    var text_top = fb.GetPlaylistName(plman.ActivePlaylist);
                    var text_bot = "This playlist is empty";
                } else {
                    var text_top = "Br3tt's WSH Playlist Viewer";
                    var text_bot = "Create a playlist to start!";
                };
                // if empty playlist, display text info
                gr.SetTextRenderingHint(5);
                gr.DrawString(text_top, gdi.Font("tahoma", 17, 0), g_color_normal_txt & 0x40ffffff, 0, -20, ww, wh, cc_stringformat);
                gr.DrawString(text_bot, gdi.Font("tahoma", 13, 0), g_color_normal_txt & 0x40ffffff, 0, +20, ww, wh, cc_stringformat);
                gr.FillGradRect(40, Math.floor(wh/2), ww-80, 1, 0, 0, g_color_normal_txt & 0x40ffffff, 0.5);
            }
        }
        
        // scrollbar
        if(properties.showscrollbar && g_scrollbar && g_list.totalRows > 0 && (g_list.totalRows > g_list.totalRowVisible)) {
            g_scrollbar.visible = true;
            g_scrollbar.draw(gr);
        } else {
            g_scrollbar.visible = false;
        }
        
        // topbar
        if(g_topbar) {
            g_topbar.draw(gr);
        }
        
        // toolbar
        if(g_toolbar) {
            g_toolbar.draw(gr);
        }
        
        // Incremental Search Display
        if(cList.search_string.length > 0) {
            gr.SetSmoothingMode(2);
            g_list.tt_x = Math.floor(((g_list.w) / 2) - (((cList.search_string.length*13)+(10*2)) / 2));
            g_list.tt_y = Math.floor((g_list.h / 2) - 25);
            g_list.tt_w = Math.round((cList.search_string.length*13)+(10*2));
            g_list.tt_h = 50;
            gr.FillRoundRect(g_list.tt_x, g_list.tt_y, g_list.tt_w, g_list.tt_h, 5, 5, RGBA(0,0,0,150));
            gr.DrawRoundRect(g_list.tt_x, g_list.tt_y, g_list.tt_w, g_list.tt_h, 5, 5, 1.0, RGBA(0,0,0,100));
            gr.DrawRoundRect(g_list.tt_x + 1, g_list.tt_y + 1, g_list.tt_w - 2, g_list.tt_h - 2, 4, 4, 1.0, RGBA(255,255,255,050));
            try {
                gr.GdiDrawText(cList.search_string, cList.incsearch_font_big, RGB(0,0,0), g_list.tt_x + 1, g_list.tt_y + 1 , g_list.tt_w , g_list.tt_h, DT_CENTER | DT_NOPREFIX | DT_CALCRECT | DT_VCENTER);
                gr.GdiDrawText(cList.search_string, cList.incsearch_font_big, g_list.inc_search_noresult?RGB(255,70,70):RGB(250,250,250), g_list.tt_x, g_list.tt_y , g_list.tt_w , g_list.tt_h, DT_CENTER | DT_NOPREFIX | DT_CALCRECT | DT_VCENTER);
            } catch(e) {}
        }
        
        // Cover Panel
        if(properties.settingspanel) {
			gr.SetSmoothingMode(0);
            gr.FillSolidRect(0, 0, ww, wh, g_color_normal_bg & 0xeeffffff);
			gr.FillSolidRect(0, 0, ww, 1, RGBA(0,0,0,30));
			//gr.FillSolidRect(0, 1, ww, wh - 1, g_color_normal_bg);
            gr.SetSmoothingMode(2);
            var ov_x = 10;
            var ov_y = 10;
            var ov_w = ww - 20;
            var ov_h = wh - 20;
            /*
            gr.FillRoundRect(ov_x, ov_y, ov_w, ov_h, 5, 5, RGBA(0,0,0,150));
            gr.DrawRoundRect(ov_x, ov_y, ov_w, ov_h, 5, 5, 1.0, RGBA(0,0,0,150));
            gr.DrawRoundRect(ov_x + 1, ov_y + 1, ov_w - 2, ov_h - 2, 4, 4, 1.0, RGBA(250,250,250,100));
            */
			g_inputbox && g_inputbox.draw(gr, 50, 50);
        }
    }
};

//=================================================// Mouse Callbacks
function on_mouse_lbtn_down(x, y) {
    
    if(properties.settingspanel) {
		// check inputbox
		g_inputbox && g_inputbox && g_inputbox.check("down", x, y);
        return true;
    }
    
    cover.previous_max_size = g_toolbar.columns[0].w;
    
    // check list
    g_list.check("down", x, y);
    
    // check scrollbar
    if(properties.showscrollbar && g_scrollbar && g_list.totalRows > 0 && (g_list.totalRows > g_list.totalRowVisible)) {
        g_scrollbar.check("down", x, y);
    }

    // check scrollbar scroll on click above or below the cursor
    if(g_scrollbar.hover && !g_scrollbar.cursorDrag) {
        var scrollstep = g_list.totalRowVisible;
        if(y < g_scrollbar.cursorPos) {
            if(!g_list.buttonclicked && !cScrollbar.timerID1) {
                g_list.buttonclicked = true;
                g_list.scrollItems(1, scrollstep);
                cScrollbar.timerID1 = window.SetTimeout(function () {
                    g_list.scrollItems(1, scrollstep);
                    cScrollbar.timerID1 && window.ClearTimeout(cScrollbar.timerID1);
                    cScrollbar.timerID1 = false;
                    cScrollbar.timerID2 && window.ClearInterval(cScrollbar.timerID2);
                    cScrollbar.timerID2 = window.SetInterval(function () {
                        if(g_scrollbar.hover) {
                            if(mouse_x > g_scrollbar.x && g_scrollbar.cursorPos > mouse_y) {
                                g_list.scrollItems(1, scrollstep);
                            };
                        };
                    }, 60);
                }, 400);
            };
        } else {
            if(!g_list.buttonclicked && !cScrollbar.timerID1) {
                g_list.buttonclicked = true;
                g_list.scrollItems(-1, scrollstep);
                cScrollbar.timerID1 = window.SetTimeout(function () {
                    g_list.scrollItems(-1, scrollstep);
                    cScrollbar.timerID1 && window.ClearTimeout(cScrollbar.timerID1);
                    cScrollbar.timerID1 = false;
                    cScrollbar.timerID2 && window.ClearInterval(cScrollbar.timerID2);
                    cScrollbar.timerID2 = window.SetInterval(function () {
                        if(g_scrollbar.hover) {
                            if(mouse_x > g_scrollbar.x && g_scrollbar.cursorPos + g_scrollbar.cursorHeight < mouse_y) {
                                g_list.scrollItems(-1, scrollstep);
                            };
                        };
                    }, 60);
                }, 400);
            };
        };
    };
    
    // check topbar
    if(g_topbar.visible) g_topbar.check("down", x, y);

    // check toolbar
    if(g_toolbar.visible) g_toolbar.check("down", x, y);
};

function on_mouse_lbtn_dblclk(x, y, mask) {
    
    if(properties.settingspanel) {
		// check inputbox
		g_inputbox && g_inputbox.check("dblclk", x, y);
        
        return true;
    }
    
    // check list
    g_list.check("dblclk", x, y);
    
    // check scrollbar
    if(properties.showscrollbar && g_scrollbar && g_list.totalRows > 0 && (g_list.totalRows > g_list.totalRowVisible)) {
        g_scrollbar.check("dblclk", x, y);
        if(g_scrollbar.hover) {
            on_mouse_lbtn_down(x, y); // ...to have a scroll response on double clicking scrollbar area above or below the cursor!
        }
    }
    
    // check toolbar
    if(g_toolbar.visible) g_toolbar.check("dblclk", x, y);
    
};

function on_mouse_lbtn_up(x, y) {
    
    if(properties.settingspanel) {
		// check inputbox
		g_inputbox && g_inputbox.check("up", x, y);

        return true;
    }
    
    // scrollbar scrolls up and down RESET
    g_list.buttonclicked = false;
    cScrollbar.timerID1 && window.ClearTimeout(cScrollbar.timerID1);
    cScrollbar.timerID1 = false;
    cScrollbar.timerID2 && window.ClearTimeout(cScrollbar.timerID2);
    cScrollbar.timerID2 = false;
    
    // after a cover column resize, update cover image cache
    if(cover.resized == true) {
        cover.resized = false;
        // reset cache
        if(!g_first_launch) {
            g_image_cache = new image_cache;
            CollectGarbage();
        } else {
            g_first_launch = false;
        }
        update_playlist();
    }
    
    // check list
    g_list.check("up", x, y);
    
    // check scrollbar
    if(properties.showscrollbar && g_scrollbar && g_list.totalRows > 0 && (g_list.totalRows > g_list.totalRowVisible)) {
        g_scrollbar.check("up", x, y);
    }
    
    // Drop items after a drag'n drop OUTSIDE the playlist (e.g. to my WSH playlist tab manager panel)
    if(dragndrop.drag_out) {
        if(!g_list.ishover) {
            window.NotifyOthers("WSH_playlist_drag_drop", g_list.metadblist_selection);
            dragndrop.drag_out = false;
        };
    };
    
    // Drop items after a drag'n drop INSIDE the playlist
    if(dragndrop.drag_in) {
        if(g_list.ishover && dragndrop.drag_id >= 0 && dragndrop.drop_id >= 0){
            var nb_selected_items = g_list.metadblist_selection.Count;
            if(dragndrop.drop_id > dragndrop.drag_id) {
                // on pointe sur le dernier item de la selection si on move vers le bas
                var new_drag_pos = g_list.handleList.Find(g_list.metadblist_selection.item(nb_selected_items - 1));
            } else {
                // on pointe sur le 1er item de la selection si on move vers le haut
                var new_drag_pos = g_list.handleList.Find(g_list.metadblist_selection.item(0));
            };
            var move_delta = dragndrop.drop_id - new_drag_pos;
            plman.MovePlaylistSelection(g_list.playlist, move_delta);
            g_list.Repaint();
        };
    };
    dragndrop.drag_id = -1;
    dragndrop.drop_id = -1;
    dragndrop.drag_in = false;
    dragndrop.drag_out = false;
    dragndrop.timerID && window.ClearTimeout(dragndrop.timerID);
    dragndrop.timerID = false;
    //window.SetCursor(IDC_ARROW);

    // check topbar
    if(g_topbar.visible) g_topbar.check("up", x, y);

    // check toolbar
    if(g_toolbar.visible) {
        g_toolbar.check("up", x, y);
        g_toolbar.drag = false;
        g_toolbar.click = false;
        window.SetCursor(IDC_ARROW);
        on_mouse_move(x, y+1);
    }
    
    // repaint on mouse up to refresh covers just loaded
    g_list.Repaint();
};

function on_mouse_rbtn_down(x, y) {
    
    if(properties.settingspanel) {
		// check inputbox
		g_inputbox && g_inputbox.check("right", x, y);
		
        return true;
    }
    
    // check list
    g_list.check("right", x, y);
    
    // check toolbar
    if(g_toolbar.visible) g_toolbar.check("right", x, y);

};

function on_mouse_rbtn_up(x, y) {
    if(!utils.IsKeyPressed(VK_SHIFT)) {
        return true;
    };
};

function on_mouse_move(x, y) {
    
    if(properties.settingspanel) {
		// check inputbox
		g_inputbox && g_inputbox.check("move", x, y);
        
        return true;
    }
    
    g_tooltip_txt = "";
    
    //if(x==mouse_x && y==mouse_y) return true;

    // check list
    g_list.check("move", x, y);
    
    // check scrollbar
    if(properties.showscrollbar && g_scrollbar && g_list.totalRows > 0 && (g_list.totalRows > g_list.totalRowVisible)) {
        g_scrollbar.check("move", x, y);
    }
    
    // check topbar
    if(g_topbar.visible) g_topbar.check("move", x, y);

    // check toolbar
    if(!g_toolbar.click && !g_toolbar.drag && !g_toolbar.onBorder) {
        if(dragndrop.enabled && (dragndrop.drag_in || dragndrop.drag_out)) window.SetCursor(IDC_HELP); else window.SetCursor(IDC_ARROW);
    }
    g_toolbar.onBorder = false;
    if(g_toolbar.visible) g_toolbar.check("move", x, y);
    if(g_toolbar.click) {
        window.SetCursor(IDC_SIZEALL);
    }
    // autohide toolbar ?
    if(!cToolbar.locked) {
        if(!g_toolbar.visible) {
            if(x > g_toolbar.x && x < g_toolbar.x + g_toolbar.w && y > g_toolbar.y && y <= g_toolbar.y + 05) {
                if(!cToolbar.autohide_timer) {
                    cToolbar.autohide_timer = window.SetTimeout(function() {
                        if(mouse_x > g_toolbar.x && mouse_x < g_toolbar.x + g_toolbar.w && mouse_y > g_toolbar.y && mouse_y <= g_toolbar.y + cToolbar.height) {
                            // show toolbar
                            var th = cToolbar.height;
                            g_toolbar.visible = true;
                            
                            // update Size of toolbar
                            g_toolbar.setSize(0, g_topbar.h, ww - cScrollbar.width, th);
                            
                            // update Size of list
                            g_list.setSize(0, g_topbar.h + th, ww, wh - g_topbar.h - th);
                            g_list.setItems(false);
                            
                            // update Size of scrollbar
                            g_scrollbar.setSize(ww - cScrollbar.width, g_topbar.h + th, cScrollbar.width, wh - g_topbar.h - th);
                            g_scrollbar.setCursor(g_list.totalRowVisible, g_list.totalRows, g_list.offset);

                            cToolbar.autohide_timer && window.ClearTimeout(cToolbar.autohide_timer);
                            cToolbar.autohide_timer = false;
                            
                            window.Repaint();
                        }
                        
                    }, 500);
                }
            } else if(cToolbar.autohide_timer) {
                cToolbar.autohide_timer && window.ClearTimeout(cToolbar.autohide_timer);
                cToolbar.autohide_timer = false;
            }
        } else if(!g_toolbar.click && !g_toolbar.drag) {
            if(!(y > g_toolbar.y && y <= g_toolbar.y + cToolbar.height)) {
                if(!cToolbar.autohide_timer) {
                    cToolbar.autohide_timer = window.SetTimeout(function() {
                        if(!(mouse_y > g_toolbar.y && mouse_y <= g_toolbar.y + cToolbar.height)) {
                            // hide toolbar
                            var th = 1;
                            // if last row = last item, update offset or there will be a bug because last row will be void!
                            if(g_list.offset >= g_list.totalRows - g_list.totalRowVisible) {
                                g_list.offset--;
                            }
                            g_toolbar.visible = false;
                            
                            // update Size of toolbar
                            g_toolbar.setSize(0, g_topbar.h, ww - cScrollbar.width, th);
                            
                            // update Size of list
                            g_list.setSize(0, g_topbar.h + th, ww, wh - g_topbar.h - th);
                            g_list.setItems(false);
                            
                            // update Size of scrollbar
                            g_scrollbar.setSize(ww - cScrollbar.width, g_topbar.h + th, cScrollbar.width, wh - g_topbar.h - th);
                            g_scrollbar.setCursor(g_list.totalRowVisible, g_list.totalRows, g_list.offset);

                            cToolbar.autohide_timer && window.ClearTimeout(cToolbar.autohide_timer);
                            cToolbar.autohide_timer = false;
                            
                            window.Repaint();
                        }
                        
                    }, 500);
                }
            } else if(cToolbar.autohide_timer) {
                cToolbar.autohide_timer && window.ClearTimeout(cToolbar.autohide_timer);
                cToolbar.autohide_timer = false;
            }
        }
    }
    
    // if cover column resized (or init), refresh column cover, minimum count, ... and playlist
    if(cover.previous_max_size != g_toolbar.columns[0].w) {
        cover.resized = true;
		if(g_toolbar.columns[0].w > 0) {
			cover.column = true;
			cGroup.count_minimum = Math.ceil( (g_toolbar.columns[0].w) / cTrack.height);
            if(cGroup.count_minimum < cGroup.default_count_minimum) cGroup.count_minimum = cGroup.default_count_minimum;
		} else {
			cover.column = false;
			cGroup.count_minimum = cGroup.default_count_minimum;
		}
        cover.previous_max_size = g_toolbar.columns[0].w;
    }
    
    // Drag'n Drop
    if(dragndrop.drag_in) {
        if(y < g_toolbar.y + g_toolbar.h && y > g_toolbar.y + g_toolbar.h - cTrack.height) {
            if(!g_list.buttonclicked) {
                g_list.buttonclicked = true;
                cScrollbar.timerID1 = window.SetInterval(function () {
                    on_mouse_wheel(1);
                }, 100);
            }  else {
                g_list.Repaint();
            }
        } else if(y > g_list.y + g_list.h && y < g_list.y + g_list.h + cTrack.height) {
            if(!g_list.buttonclicked) {
                g_list.buttonclicked = true;
                cScrollbar.timerID1 = window.SetInterval(function () {
                    on_mouse_wheel(-1);
                }, 100);
            } else {
                g_list.Repaint();
            }
        } else {
            cScrollbar.timerID1 && window.ClearInterval(cScrollbar.timerID1);
            cScrollbar.timerID1 = false;
            g_list.buttonclicked = false;
            if(!dragndrop.timerID) {
                dragndrop.timerID = window.SetTimeout(function() {
                    g_list.Repaint();
                    dragndrop.timerID && window.ClearTimeout(dragndrop.timerID);
                    dragndrop.timerID = false;
                }, 70);
            };
        };
    };

    
    // tooltip
	if(properties.showtooltip) {
		if(g_tooltip.Text != g_tooltip_txt) {
			g_tooltip.Deactivate();
			g_tooltip.Text = g_tooltip_txt;
			g_tooltip.Activate();
		}
	}
    
    mouse_x = x;
    mouse_y = y;
};

function on_mouse_wheel(delta) {
    
    if(properties.settingspanel) {
        
        return true;
    }
    
    reset_cover_timers();
        
    // timer to tell to other functions (on cover load asynch done, ...) that a repaint is already running 
    if(!g_mouse_wheel_timer) {
        g_mouse_wheel_timer = window.SetTimeout(function() {
            var cw = cover.column ? ((g_toolbar.columns[0].w <= cover.max_w) ? cover.max_w : g_toolbar.columns[0].w) : cover.max_w;
            var ch = cw;
            g_list.scrollItems(delta, g_list.scrollstep);
            g_mouse_wheel_timer && window.ClearTimeout(g_mouse_wheel_timer);
            g_mouse_wheel_timer = false;
        }, 15);
    }
};

function on_mouse_leave() {
    
    if(properties.settingspanel) {
        
        return true;
    }
    
    g_list.check("leave", 0, 0);
    if(properties.showscrollbar && g_scrollbar && g_list.totalRows > 0 && (g_list.totalRows > g_list.totalRowVisible)) {
        g_scrollbar.check("leave", 0, 0);
    }
    
    // tooltip
    g_tooltip.Text = "";
    g_tooltip.Deactivate();
};

//=================================================// Callbacks

function update_playlist() {
    g_list.updateHandleList(plman.ActivePlaylist);
    g_group_id_focused = 0;
    g_list.setItems(false);
    g_scrollbar.setCursor(g_list.totalRowVisible, g_list.totalRows, g_list.offset);
    g_list.Repaint();
}

function on_playlist_switch() {
    update_playlist();
    g_topbar.Repaint();
};

function on_playlists_changed() {
    if(plman.ActivePlaylist < 0 || plman.ActivePlaylist > plman.PlaylistCount - 1) {
        plman.ActivePlaylist = 0;
    }
    g_list.playlist = plman.ActivePlaylist;
};

function on_playlist_items_added(playlist_idx) {
    if(playlist_idx == g_list.playlist) {
        update_playlist();
        g_topbar.Repaint();
    }
};

function on_playlist_items_removed(playlist_idx, new_count) {
    if(playlist_idx == g_list.playlist) {
        update_playlist();
        g_topbar.Repaint();
    }
};

function on_playlist_items_reordered(playlist_idx) {
    if(playlist_idx == g_list.playlist) {
        update_playlist();
    }
};

function on_selection_changed(metadb) {

};

function on_playlist_items_selection_change() {
    g_list.Repaint();
};

function on_item_focus_change(playlist, from, to) {
    if(playlist == g_list.playlist) {
        g_list.focusedTrackId = to;
        plman.SetActivePlaylistContext();
        var center_focus_item = g_list.isFocusedItemVisible();

        if(properties.autocollapse && !center_focus_item) {
            var grpId = g_list.getGroupIdfromTrackId(to);
            if(grpId >= 0) {
                g_list.updateGroupStatus(grpId);
                g_list.setItems(false);
                center_focus_item = g_list.isFocusedItemVisible();
            }
        }

        if(!center_focus_item && !g_list.drawRectSel) {
            g_list.setItems(true);
        }
        g_scrollbar.setCursor(g_list.totalRowVisible, g_list.totalRows, g_list.offset);
        g_list.Repaint();
    }
};

function on_metadb_changed(metadb_or_metadbs, fromhook) {
    var tmp;   
    // rebuild list to draw
    g_list.setItems(false);
    // redraw
    g_list.Repaint();
};

//=================================================// Playback Callbacks
function on_playback_new_track(metadb) {
    if(properties.showwallpaper) {
        g_wallpaper_img = setWallpaperImg(properties.wallpaperpath, metadb);
    }
    
    if(properties.autocollapse) {
        if(plman.PlayingPlaylist == g_list.playlist) {
            plman.SetPlaylistFocusItemByHandle(g_list.playlist, metadb);
            var nowplaying = plman.GetPlayingItemLocation();
            g_list.focusedTrackId = plman.GetPlaylistFocusItemIndex(g_list.playlist);
            if(nowplaying.PlaylistItemIndex == g_list.focusedTrackId) {
                var grpId = g_list.getGroupIdfromTrackId(g_list.focusedTrackId);
                if(grpId >= 0) { 
                    g_list.updateGroupStatus(grpId);
                    var center_focus_item = g_list.isFocusedItemVisible();
                    if(!center_focus_item) {
                        g_list.setItems(!center_focus_item);
                        g_scrollbar.setCursor(g_list.totalRowVisible, g_list.totalRows, g_list.offset);
                    }
                }
            }
        }
    }
    g_list.Repaint();
};

function on_playback_starting(cmd, is_paused) {
    
}

function on_playback_stop(reason) {
    switch(reason) {
    case 0: // user stop
    case 1: // eof
        if(properties.showwallpaper) {
            g_wallpaper_img = setWallpaperImg(properties.wallpaperpath, null);
        }
        g_list.Repaint();
        break;
    case 2: // track change

        break;
    }
};

function on_playback_pause(state){
    window.RepaintRect(g_list.x, g_list.nowplaying_y, g_list.w, cTrack.height);
};

function on_playback_seek(time) {

};

function on_playback_time(time) {
    g_seconds = time;
    window.RepaintRect(g_list.x, g_list.nowplaying_y, g_list.w, cTrack.height);
};

//=================================================// Keyboard Callbacks

function on_key_up(vkey) {
    // scroll keys up and down RESET (step and timers)
    g_list.keypressed = false;
    cScrollbar.timerID1 && window.ClearTimeout(cScrollbar.timerID1);
    cScrollbar.timerID1 = false;
    cScrollbar.timerID2 && window.ClearTimeout(cScrollbar.timerID2);
    cScrollbar.timerID2 = false;
    if(vkey == VK_SHIFT) {
        g_list.SHIFT_start_id = null;
        g_list.SHIFT_count = 0;
    };
};

function on_key_down(vkey) {

	if(properties.settingspanel) {
		g_inputbox && g_inputbox.on_key_down(vkey);
		return true;
	}
    
    if(dragndrop.drag_in) return true;
    
    var mask = GetKeyboardMask();
    var act_pls = plman.ActivePlaylist;
    
    if (mask == KMask.none) {
        switch (vkey) {
        case VK_SHIFT:
            g_list.SHIFT_count = 0;
            break;
        case VK_BACK:
            if(cList.search_string.length>0) {
                g_list.tt_x = ((g_list.w) / 2) - (((cList.search_string.length*13)+(10*2)) / 2);
                g_list.tt_y = (g_list.h / 2) - 30;
                g_list.tt_w = ((cList.search_string.length*13)+(10*2));
                g_list.tt_h = 60;
                cList.search_string = cList.search_string.substring(0, cList.search_string.length - 1);
                window.RepaintRect(0, g_list.tt_y - 2, g_list.w, g_list.tt_h + 4);
                cList.clear_incsearch_timer && window.ClearInterval(cList.clear_incsearch_timer);
                cList.incsearch_timer && window.ClearTimeout(cList.incsearch_timer);
                cList.incsearch_timer = window.SetTimeout(function () {
                    g_list.incrementalSearch();
                    cList.incsearch_timer && window.ClearTimeout(cList.incsearch_timer);
                    cList.incsearch_timer = false;
                }, 400);
            };
            break;
        case VK_ESCAPE:
        case 222:
            g_list.tt_x = ((g_list.w) / 2) - (((cList.search_string.length*13)+(10*2)) / 2);
            g_list.tt_y = (g_list.h/2) - 30;
            g_list.tt_w = ((cList.search_string.length*13)+(10*2));
            g_list.tt_h = 60;
            cList.search_string = "";
            window.RepaintRect(0, g_list.tt_y - 2, g_list.w, g_list.tt_h + 4);
            break;
        case VK_UP:
            var scrollstep = 1;
            var new_focus_id = 0;
            if(!g_list.keypressed && !cScrollbar.timerID1) {
                g_list.keypressed = true;
                reset_cover_timers();
                new_focus_id = (g_list.focusedTrackId > 0) ? g_list.focusedTrackId - scrollstep : 0;
                // if new track focused id is in a collapsed group, set the 1st track of the group as the focused track (= group focused)
                var grpId = g_list.getGroupIdfromTrackId(new_focus_id);
                if(g_list.groups[grpId].collapsed) {
                    new_focus_id = g_list.groups[grpId].start;
                }
                if(g_list.focusedTrackId == 0 && g_list.offset > 0) {
                    g_list.scrollItems(1, scrollstep);
                    cScrollbar.timerID1 = window.SetTimeout(function () {
                        g_list.scrollItems(1, scrollstep);
                        cScrollbar.timerID1 && window.ClearTimeout(cScrollbar.timerID1);
                        cScrollbar.timerID1 = false;
                        cScrollbar.timerID2 && window.ClearInterval(cScrollbar.timerID2);
                        cScrollbar.timerID2 = window.SetInterval(function () {
                            g_list.scrollItems(1, scrollstep);
                        }, 50);
                    }, 400);
                } else {
                    plman.SetPlaylistFocusItem(act_pls, new_focus_id);
                    plman.ClearPlaylistSelection(act_pls);
                    plman.SetPlaylistSelectionSingle(act_pls, new_focus_id, true);
                    cScrollbar.timerID1 = window.SetTimeout(function () {
                        cScrollbar.timerID1 && window.ClearTimeout(cScrollbar.timerID1);
                        cScrollbar.timerID1 = false;
                        cScrollbar.timerID2 && window.ClearInterval(cScrollbar.timerID2);
                        cScrollbar.timerID2 = window.SetInterval(function () {
                            new_focus_id = (g_list.focusedTrackId > 0) ? g_list.focusedTrackId - scrollstep : 0;
                            // if new track focused id is in a collapsed group, set the 1st track of the group as the focused track (= group focused)
                            var grpId = g_list.getGroupIdfromTrackId(new_focus_id);
                            if(g_list.groups[grpId].collapsed) {
                                new_focus_id = g_list.groups[grpId].start;
                            }
                            plman.SetPlaylistFocusItem(act_pls, new_focus_id);
                            plman.ClearPlaylistSelection(act_pls);
                            plman.SetPlaylistSelectionSingle(act_pls, new_focus_id, true);
                        }, 50);
                    }, 400);
                }
            }
            break;
        case VK_DOWN:
            var new_focus_id = 0;
            if(!g_list.keypressed && !cScrollbar.timerID1) {
                g_list.keypressed = true;
                reset_cover_timers();
                new_focus_id = (g_list.focusedTrackId < g_list.count - 1) ? g_list.focusedTrackId + 1 : g_list.count - 1;
                // if new track focused id is in a collapsed group, set the last track of the group as the focused track (= group focused)
                var grpId = g_list.getGroupIdfromTrackId(new_focus_id);
                if(g_list.groups[grpId].collapsed) {
                    new_focus_id = g_list.groups[grpId].start + g_list.groups[grpId].count - 1;
                }
                plman.SetPlaylistFocusItem(act_pls, new_focus_id);
                plman.ClearPlaylistSelection(act_pls);
                plman.SetPlaylistSelectionSingle(act_pls, new_focus_id, true);
                cScrollbar.timerID1 = window.SetTimeout(function () {
                    cScrollbar.timerID1 && window.ClearTimeout(cScrollbar.timerID1);
                    cScrollbar.timerID1 = false;
                    cScrollbar.timerID2 && window.ClearInterval(cScrollbar.timerID2);
                    cScrollbar.timerID2 = window.SetInterval(function () {
                        new_focus_id = (g_list.focusedTrackId < g_list.count - 1) ? g_list.focusedTrackId + 1 : g_list.count - 1;
                        // if new track focused id is in a collapsed group, set the last track of the group as the focused track (= group focused)
                        var grpId = g_list.getGroupIdfromTrackId(new_focus_id);
                        if(g_list.groups[grpId].collapsed) {
                            new_focus_id = g_list.groups[grpId].start + g_list.groups[grpId].count - 1;
                        }
                        plman.SetPlaylistFocusItem(act_pls, new_focus_id);
                        plman.ClearPlaylistSelection(act_pls);
                        plman.SetPlaylistSelectionSingle(act_pls, new_focus_id, true);
                    }, 50);
                }, 400);
            };
            break;
        case VK_PGUP:
            var scrollstep = g_list.totalRowVisible;
            var new_focus_id = 0;
            if(!g_list.keypressed && !cScrollbar.timerID1) {
                g_list.keypressed = true;
                reset_cover_timers();
                new_focus_id = (g_list.focusedTrackId > scrollstep) ? g_list.focusedTrackId - scrollstep : 0;
                if(g_list.focusedTrackId == 0 && g_list.offset > 0) {
                    g_list.scrollItems(1, scrollstep);
                    cScrollbar.timerID1 = window.SetTimeout(function () {
                        g_list.scrollItems(1, scrollstep);
                        cScrollbar.timerID1 && window.ClearTimeout(cScrollbar.timerID1);
                        cScrollbar.timerID1 = false;
                        cScrollbar.timerID2 && window.ClearInterval(cScrollbar.timerID2);
                        cScrollbar.timerID2 = window.SetInterval(function () {
                            g_list.scrollItems(1, scrollstep);
                        }, 60);
                    }, 400);
                } else {
                    plman.SetPlaylistFocusItem(act_pls, new_focus_id);
                    plman.ClearPlaylistSelection(act_pls);
                    plman.SetPlaylistSelectionSingle(act_pls, new_focus_id, true);
                    cScrollbar.timerID1 = window.SetTimeout(function () {
                        cScrollbar.timerID1 && window.ClearTimeout(cScrollbar.timerID1);
                        cScrollbar.timerID1 = false;
                        cScrollbar.timerID2 && window.ClearInterval(cScrollbar.timerID2);
                        cScrollbar.timerID2 = window.SetInterval(function () {
                            new_focus_id = (g_list.focusedTrackId > scrollstep) ? g_list.focusedTrackId - scrollstep : 0;
                            plman.SetPlaylistFocusItem(act_pls, new_focus_id);
                            plman.ClearPlaylistSelection(act_pls);
                            plman.SetPlaylistSelectionSingle(act_pls, new_focus_id, true);
                        }, 60);
                    }, 400);
                }
            }
            break;
        case VK_PGDN:
            var scrollstep = g_list.totalRowVisible;
            var new_focus_id = 0;
            if(!g_list.keypressed && !cScrollbar.timerID1) {
                g_list.keypressed = true;
                reset_cover_timers();
                new_focus_id = (g_list.focusedTrackId < g_list.count - scrollstep) ? g_list.focusedTrackId + scrollstep : g_list.count - 1;
                plman.SetPlaylistFocusItem(act_pls, new_focus_id);
                plman.ClearPlaylistSelection(act_pls);
                plman.SetPlaylistSelectionSingle(act_pls, new_focus_id, true);
                cScrollbar.timerID1 = window.SetTimeout(function () {
                    cScrollbar.timerID1 && window.ClearTimeout(cScrollbar.timerID1);
                    cScrollbar.timerID1 = false;
                    cScrollbar.timerID2 && window.ClearInterval(cScrollbar.timerID2);
                    cScrollbar.timerID2 = window.SetInterval(function () {
                        new_focus_id = (g_list.focusedTrackId < g_list.count - scrollstep) ? g_list.focusedTrackId + scrollstep : g_list.count - 1;
                        plman.SetPlaylistFocusItem(act_pls, new_focus_id);
                        plman.ClearPlaylistSelection(act_pls);
                        plman.SetPlaylistSelectionSingle(act_pls, new_focus_id, true);
                    }, 60);
                }, 400);
            };
            break;
        case VK_RETURN:
            // play focus item
            if(!isQueuePlaylistActive()) {
                plman.ExecutePlaylistDefaultAction(act_pls, g_list.focusedTrackId);
            };
            break;
        case VK_END:
            plman.SetPlaylistFocusItem(act_pls, g_list.count - 1);
            plman.ClearPlaylistSelection(act_pls);
            plman.SetPlaylistSelectionSingle(act_pls, g_list.count - 1, true);
            break;
        case VK_HOME:
            plman.SetPlaylistFocusItem(act_pls, 0);
            plman.ClearPlaylistSelection(act_pls);
            plman.SetPlaylistSelectionSingle(act_pls, 0, true);
            break;
        case VK_DELETE:
            if(!fb.IsAutoPlaylist(act_pls)) {
                ///*
                if(isQueuePlaylistActive()) {
                    var affected_items = Array();
                    var first_focus_id = null;
                    var next_focus_id = null;
                    for(var k=0; k < g_list.count; k++) {
                        if(plman.IsPlaylistItemSelected(act_pls, k)) {
                            affected_items.push(k);
                            if(first_focus_id == null) fist_focus_id = k;
                            next_focus_id = k + 1;
                        };
                    };
                    if(next_focus_id >= g_list.count) {
                        next_focus_id = first_focus_id;
                    };
                    if(next_focus_id != null) {
                        plman.SetPlaylistFocusItem(act_pls, next_focus_id);
                        plman.SetPlaylistSelectionSingle(act_pls, next_focus_id, true);
                    };
                    plman.RemoveItemsFromPlaybackQueue(affected_items);
                } else {
                    plman.RemovePlaylistSelection(act_pls, false);
                };
                //*/
                plman.RemovePlaylistSelection(act_pls, false);
                plman.SetPlaylistSelectionSingle(act_pls, plman.GetPlaylistFocusItemIndex(act_pls), true);
            };
            break;
        };
    } else {
        switch(mask) {
            case KMask.shift:
                if(vkey==VK_UP) { // SHIFT + KEY UP
                    if(g_list.SHIFT_count==0) {
                        if(g_list.SHIFT_start_id==null) {
                            g_list.SHIFT_start_id = g_list.focusedTrackId;
                        };
                        plman.ClearPlaylistSelection(act_pls);
                        plman.SetPlaylistSelectionSingle(act_pls, g_list.focusedTrackId, true);
                        if(g_list.focusedTrackId > 0) {
                            g_list.SHIFT_count--;
                            g_list.focusedTrackId--;
                            plman.SetPlaylistSelectionSingle(act_pls, g_list.focusedTrackId, true);
                            plman.SetPlaylistFocusItem(act_pls, g_list.focusedTrackId);
                        };
                    } else if(g_list.SHIFT_count < 0) {
                        if(g_list.focusedTrackId > 0) {
                            g_list.SHIFT_count--;
                            g_list.focusedTrackId--;
                            plman.SetPlaylistSelectionSingle(act_pls, g_list.focusedTrackId, true);
                            plman.SetPlaylistFocusItem(act_pls, g_list.focusedTrackId);
                        };
                    } else {
                        plman.SetPlaylistSelectionSingle(act_pls, g_list.focusedTrackId, false);
                        g_list.SHIFT_count--;
                        g_list.focusedTrackId--;
                        plman.SetPlaylistFocusItem(act_pls, g_list.focusedTrackId);
                    };
                };
                if(vkey==VK_DOWN) { // SHIFT + KEY DOWN
                    if(g_list.SHIFT_count==0) {
                        if(g_list.SHIFT_start_id == null) {
                            g_list.SHIFT_start_id = g_list.focusedTrackId;
                        };
                        plman.ClearPlaylistSelection(act_pls);
                        plman.SetPlaylistSelectionSingle(act_pls, g_list.focusedTrackId, true);
                        if(g_list.focusedTrackId < g_list.count - 1) {
                            g_list.SHIFT_count++;
                            g_list.focusedTrackId++;
                            plman.SetPlaylistSelectionSingle(act_pls, g_list.focusedTrackId, true);
                            plman.SetPlaylistFocusItem(act_pls, g_list.focusedTrackId);
                        };
                    } else if(g_list.SHIFT_count>0) {
                        if(g_list.focusedTrackId < g_list.count - 1) {
                            g_list.SHIFT_count++;
                            g_list.focusedTrackId++;
                            plman.SetPlaylistSelectionSingle(act_pls, g_list.focusedTrackId, true);
                            plman.SetPlaylistFocusItem(act_pls, g_list.focusedTrackId);
                        };
                    } else {
                        plman.SetPlaylistSelectionSingle(act_pls, g_list.focusedTrackId, false);
                        g_list.SHIFT_count++;
                        g_list.focusedTrackId++;
                        plman.SetPlaylistFocusItem(act_pls, g_list.focusedTrackId);
                    };
                };
                break;
            case KMask.ctrl:
                if(vkey==65) { // CTRL+A
                    fb.RunMainMenuCommand("Edit/Select all");
                    window.Repaint();
                };
                if(vkey==67) { // CTRL+C
                    clipboard.selection = plman.GetPlaylistSelectedItems(plman.ActivePlaylist);
                };
                if(vkey==86) { // CTRL+V
                    // insert the clipboard selection (handles) after the current position in the active playlist
                    if(clipboard.selection) {
                        if(clipboard.selection.Count>0) {
                            try {
                                if(g_list.count > 0) {
                                    plman.InsertPlaylistItems(plman.ActivePlaylist, g_list.focusedTrackId + 1, clipboard.selection);
                                } else {
                                    plman.InsertPlaylistItems(plman.ActivePlaylist, 0, clipboard.selection);
                                };
                            } catch(e) {
                                fb.trace("WSH Playlist WARNING: Clipboard can't be paste, invalid clipboard content.");
                            };
                        };
                    };
                };
                if(vkey==70) { // CTRL+F
                    fb.RunMainMenuCommand("Edit/Search");
                };
                if(vkey==78) { // CTRL+N
                    fb.RunMainMenuCommand("File/New playlist");
                };
                if(vkey==79) { // CTRL+O
                    fb.RunMainMenuCommand("File/Open...");
                };
                if(vkey==80) { // CTRL+P
                    fb.RunMainMenuCommand("File/Preferences");
                };
                if(vkey==83) { // CTRL+S
                    fb.RunMainMenuCommand("File/Save playlist...");
                };
                if(vkey==84) { // CTRL+T
                    // Toggle Toolbar
                    if(g_toolbar.visible) {
                        var th = 1;
                        // if last row = last item, update offset or there will be a bug because last row will be void!
                        if(g_list.offset >= g_list.totalRows - g_list.totalRowVisible) {
                            g_list.offset--;
                        }
                    } else {
                        var th = cToolbar.height;
                    }
                    g_toolbar.visible = !g_toolbar.visible;
                                        
                    // update Size of toolbar
                    g_toolbar.setSize(0, g_topbar.h, ww - cScrollbar.width, th);
                    //g_toolbar.setColumns();
                    
                    // update Size of list
                    g_list.setSize(0, g_topbar.h + th, ww, wh - g_topbar.h - th);
                    g_list.setItems(false);
                    
                    // update Size of scrollbar
                    g_scrollbar.setSize(ww - cScrollbar.width, g_topbar.h + th, cScrollbar.width, wh - g_topbar.h - th);
                    g_scrollbar.setCursor(g_list.totalRowVisible, g_list.totalRows, g_list.offset);

                    window.Repaint();
                };
                break;
            case KMask.alt:
                if(vkey==65) { // ALT+A
                    fb.RunMainMenuCommand("View/Always on Top");
                };
                break;
        };
    };
};

function on_char(code) {

	if(properties.settingspanel) {
		g_inputbox && g_inputbox.on_char(code);
		return true;
	}

    if(g_list.count > 0) {
        g_list.tt_x = ((g_list.w) / 2) - (((cList.search_string.length*13)+(10*2)) / 2);
        g_list.tt_y = (g_list.h / 2) - 30;
        g_list.tt_w = ((cList.search_string.length*13)+(10*2));
        g_list.tt_h = 60;
        if(code==32 && cList.search_string.length==0) return true; // SPACE Char not allowed on 1st char
        if(cList.search_string.length <= 20 && g_list.tt_w <= g_list.w - 20) {
            if (code > 31) {
                cList.search_string = cList.search_string + String.fromCharCode(code).toUpperCase();
                window.RepaintRect(0, g_list.tt_y - 2, ww, g_list.tt_h + 4);
                cList.clear_incsearch_timer && window.ClearInterval(cList.clear_incsearch_timer);
                cList.clear_incsearch_timer = false;
                cList.incsearch_timer && window.ClearTimeout(cList.incsearch_timer);
                cList.incsearch_timer = window.SetTimeout(function () {
                    g_list.incrementalSearch();
                    cList.incsearch_timer && window.ClearTimeout(cList.incsearch_timer);
                    cList.incsearch_timer = false;
                }, 400);
            };
        };
    };
};

//=================================================// Extra functions
function get_images() {
    var gb;
    var gui_font = gdi.Font("guifx v2 transports", 15, 0);
    
    images.glass_reflect = draw_glass_reflect(400, 400);

    images.pauseicon = gdi.CreateImage(20, cTrack.height);
    gb = images.pauseicon.GetGraphics();
    var x1 = 1;
    var y = Math.floor(cTrack.height / 2) - 6;
    var x2 = 8;
    gb.FillSolidRect(x1, y, 5, 12, RGB(255,255,255));
    gb.DrawRect(x1, y, 4, 11, 1.0, g_color_normal_txt & 0x50ffffff);
    gb.FillSolidRect(x2, y, 5, 12, RGB(255,255,255));
    gb.DrawRect(x2, y, 4, 11, 1.0, g_color_normal_txt & 0x50ffffff);
    images.pauseicon.ReleaseGraphics(gb);
       
    images.playicon_off = gdi.CreateImage(20, cTrack.height);
    gb = images.playicon_off.GetGraphics();
    gb.SetSmoothingMode(2);
    var x1 = 0;
    var y1 = Math.floor(cTrack.height / 2) - 6;
    var x2 = 12;
    var y2 = Math.floor(cTrack.height / 2);
    var x3 = 0;
    var y3 = Math.floor(cTrack.height / 2) + 6;
    var points = Array(x1, y1, x2, y2, x3, y3);
    gb.FillPolygon(RGB(255,255,255), 0, points);
    gb.DrawPolygon(g_color_normal_txt & 0x50ffffff, 1.0, points);
    gb.SetSmoothingMode(0);
    images.playicon_off.ReleaseGraphics(gb);

    images.playicon_on = gdi.CreateImage(20, cTrack.height);
    gb = images.playicon_on.GetGraphics();
    gb.SetSmoothingMode(2);
    var x1 = 0;
    var y1 = Math.floor(cTrack.height / 2) - 6;
    var x2 = 12;
    var y2 = Math.floor(cTrack.height / 2);
    var x3 = 0;
    var y3 = Math.floor(cTrack.height / 2) + 6;
    var points = Array(x1, y1, x2, y2, x3, y3);
    gb.FillPolygon(g_color_normal_bg, 0, points);
    gb.FillPolygon(g_color_selected_bg & 0x80ffffff, 0, points);
    gb.DrawPolygon(g_color_selected_bg, 1.0, points);
    gb.DrawPolygon(RGBA(0,0,0,60), 1.0, points);
    gb.SetSmoothingMode(0);
    images.playicon_on.ReleaseGraphics(gb);

    images.nocover = gdi.Image(path_img+"nocover.png");
    images.noartist = gdi.Image(path_img+"noartist.png");
    images.stream = gdi.Image(path_img+"stream.png");
}

//=================================================// Queue Playlist features
function isQueuePlaylistActive() {
    var queue_pl_idx = isQueuePlaylistPresent();
    if(queue_pl_idx<0) {
        return false;
    } else if(plman.ActivePlaylist == queue_pl_idx) {
        return true;
    };
};

function isQueuePlaylistPresent() {
    for(var i=0; i<plman.PlaylistCount; i++) {
        if(plman.GetPlaylistName(i)=="Queue Content") return i;
    };
    return -1;    
};

function SetPlaylistQueue() {
    var total_pl = plman.PlaylistCount;
    var queue_pl_idx = isQueuePlaylistPresent();
    if(queue_pl_idx<0) {
        return true;
    } else {
        var total_in_pls = plman.PlaylistItemCount(queue_pl_idx);
        if(total_in_pls > 0) {
            var affected_items = Array();
            for(var i=0; i<total_in_pls; i++) {
                affected_items.push(i);
            };
            plman.SetPlaylistSelection(queue_pl_idx, affected_items, true);
            plman.RemovePlaylistSelection(queue_pl_idx);
        };
    };
    var queue_total = plman.GetPlaybackQueueCount();
    var vbarr = plman.GetPlaybackQueueContents();
    var arr = vbarr.toArray();
    var q_handlelist = plman.GetPlaylistSelectedItems(queue_pl_idx);
    q_handlelist.RemoveAll();
    for(var j=0; j<queue_total; j++) {
        q_handlelist.Add(arr[j].Handle);
    };
    plman.InsertPlaylistItems(queue_pl_idx, j, q_handlelist, false);
};

function ShowPlaylistQueue(focus_id) {
    var total_pl = plman.PlaylistCount;
    var queue_pl_idx = isQueuePlaylistPresent();
    if(queue_pl_idx < 0) {
        plman.CreatePlaylist(total_pl, "Queue Content");
        queue_pl_idx = total_pl;
        plman.ActivePlaylist = queue_pl_idx;
    } else {
        plman.ActivePlaylist = queue_pl_idx;
        fb.ClearPlaylist();
    };
    var queue_total = plman.GetPlaybackQueueCount();
    var vbarr = plman.GetPlaybackQueueContents();
    var arr = vbarr.toArray();
    var q_handlelist = plman.GetPlaylistSelectedItems(queue_pl_idx);
    q_handlelist.RemoveAll();
    for(var i=0; i<queue_total; i++) {
        q_handlelist.Add(arr[i].Handle);
    };
    plman.InsertPlaylistItems(queue_pl_idx, i, q_handlelist, false);
    plman.SetPlaylistFocusItem(queue_pl_idx, 0);
};

function CheckPlaylistQueue() {
    var current = plman.ActivePlaylist;
    var total_pl = plman.PlaylistCount;
    var queue_pl_idx = isQueuePlaylistPresent();
    if(queue_pl_idx < 0) {
        plman.CreatePlaylist(total_pl, "Queue Content");
        queue_pl_idx = total_pl;
    } else {
        plman.RemovePlaylist(queue_pl_idx);
        plman.CreatePlaylist(queue_pl_idx, "Queue Content");
    }
    var queue_total = plman.GetPlaybackQueueCount();
    var vbarr = plman.GetPlaybackQueueContents();
    var arr = vbarr.toArray();
    var q_handlelist = plman.GetPlaylistSelectedItems(queue_pl_idx);
    q_handlelist.RemoveAll();
    for(var i=0; i<queue_total; i++) {
        q_handlelist.Add(arr[i].Handle);
    }
    plman.InsertPlaylistItems(queue_pl_idx, i, q_handlelist, false);
    plman.SetPlaylistFocusItem(queue_pl_idx, 0);
}

function on_playback_queue_changed(origin) {
    if(isQueuePlaylistActive()) {
        ShowPlaylistQueue(0);
    } else {
        SetPlaylistQueue();
    };
};

function on_focus(is_focused) {
	if(properties.settingspanel) {
		g_inputbox && g_inputbox.on_focus(is_focused);
		return true;
	}

    if(is_focused) {

    } else {
        if(g_toolbar.visible) g_toolbar.check("leave", 0, 0);
        if(g_topbar.visible) g_topbar.check("leave", 0, 0);
    };
};

function on_notify_data(name, info) {
    switch(name) {
        case "xxx":
            break;
    }
}

//=================================================// Drag'n'Drop Callbacks
var wsh_dragging = false;

function on_drag_enter() {
    wsh_dragging = true;
};

function on_drag_leave() {
    wsh_dragging = false;
};

function on_drag_over(action, x, y, mask) {
    //on_mouse_move(x, y);
};

function on_drag_drop(action, x, y, mask) {
    wsh_dragging = false;
    // We are going to process the dropped items to a playlist
    action.ToPlaylist();
    action.Playlist = plman.ActivePlaylist;
    action.ToSelect = false;
    g_topbar.Repaint();
};

function setWallpaperImg(path, metadb) {
    if(properties.showartistwallpaper) {
        if(metadb) {
            var tmp_img = utils.GetAlbumArtV2(metadb, AlbumArtId.artist);
        } else {
            tmp_img = gdi.Image(path);
        }
        if(!tmp_img) {
            tmp_img = gdi.Image(path);
        }
    } else {
        tmp_img = gdi.Image(path);
    }
    var img = FormatWallpaper(tmp_img, g_list.w, g_list.h, 2, 0, 0, "", true);
    return img;
}

function FormatWallpaper(image, iw, ih, interpolation_mode, display_mode, angle, txt, rawBitmap) {
	if(!image||!iw||!ih) return image;
    var i, j;
    
    var panel_ratio = iw / ih;
    wpp_img_info.ratio = image.Width / image.Height;
    wpp_img_info.orient = 0;

    if(wpp_img_info.ratio > panel_ratio) {
        wpp_img_info.orient = 1;
        // 1/3 : default image is in landscape mode    
        switch(display_mode) {
            case 0:     // Filling
                //wpp_img_info.w = iw * wpp_img_info.ratio / panel_ratio;
                wpp_img_info.w = ih * wpp_img_info.ratio;
                wpp_img_info.h = ih;
                wpp_img_info.cut = wpp_img_info.w - iw;
                wpp_img_info.x = 0 - (wpp_img_info.cut / 2);
                wpp_img_info.y = 0;
                break;
            case 1:     // Adjust
                wpp_img_info.w = iw;
                wpp_img_info.h = ih / wpp_img_info.ratio * panel_ratio;
                wpp_img_info.cut = ih - wpp_img_info.h;
                wpp_img_info.x = 0;
                wpp_img_info.y = wpp_img_info.cut / 2;
                break;
            case 2:     // Stretch
                wpp_img_info.w = iw;
                wpp_img_info.h = ih;
                wpp_img_info.cut = 0;
                wpp_img_info.x = 0;
                wpp_img_info.y = 0;
                break;
        }
    } else if(wpp_img_info.ratio < panel_ratio) {
        wpp_img_info.orient = 2;
        // 2/3 : default image is in portrait mode
        switch(display_mode) {
            case 0:     // Filling
                wpp_img_info.w = iw;
                //wpp_img_info.h = ih / wpp_img_info.ratio * panel_ratio;
                wpp_img_info.h = iw / wpp_img_info.ratio;
                wpp_img_info.cut = wpp_img_info.h - ih;
                wpp_img_info.x = 0;
                wpp_img_info.y = 0 - (wpp_img_info.cut / 4);
                break;
            case 1:     // Adjust
                wpp_img_info.h = ih;
                wpp_img_info.w = iw * wpp_img_info.ratio / panel_ratio;
                wpp_img_info.cut = iw - wpp_img_info.w;
                wpp_img_info.y = 0;
                wpp_img_info.x = wpp_img_info.cut / 2;
                break;
            case 2:     // Stretch
                wpp_img_info.w = iw;
                wpp_img_info.h = ih;
                wpp_img_info.cut = 0;
                wpp_img_info.x = 0;
                wpp_img_info.y = 0;
                break;
        }
    } else {
        // 3/3 : default image is a square picture, ratio = 1
        wpp_img_info.w = iw;
        wpp_img_info.h = ih;
        wpp_img_info.cut = 0;
        wpp_img_info.x = 0;
        wpp_img_info.y = 0;
    }

    var tmp_img = gdi.CreateImage(iw, ih);
    var gp = tmp_img.GetGraphics();
	gp.SetInterpolationMode(interpolation_mode);
    gp.DrawImage(image, wpp_img_info.x, wpp_img_info.y, wpp_img_info.w, wpp_img_info.h, 0, 0, image.Width, image.Height, angle, 255);
	tmp_img.ReleaseGraphics(gp);
	CollectGarbage();
    if(rawBitmap) {
        return tmp_img.CreateRawBitmap();
    } else {
        return tmp_img;
    }
}